/*
	board.cc	Game Board Operations
	Copyright (c) 2000-2005, 2006 Kriang Lerdsuwanakij
	email:		lerdsuwa@users.sourceforge.net

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "board.h"
#include "iter.h"
#include "hash.h"
#include <string.h>
#include <exception>
#include <stdexcept>

#ifdef _
#  undef _
#endif

#ifdef N_
#  undef N_
#endif

#include <libintl.h>
#define _(x) gettext(x)
#define N_(x) (x)

#include "table-dat.h"

byte_board_type board_empty = {
	 0, 0, 0, 0, 0, 0, 0, 0, 
	 0, 0, 0, 0, 0, 0, 0, 0, 
	 0, 0, 0, 0, 0, 0, 0, 0, 
	 0, 0, 0, 0, 0, 0, 0, 0, 
	 0, 0, 0, 0, 0, 0, 0, 0, 
	 0, 0, 0, 0, 0, 0, 0, 0, 
	 0, 0, 0, 0, 0, 0, 0, 0, 
	 0, 0, 0, 0, 0, 0, 0, 0, 
};

byte_board_type board_begin = {
	 0, 0, 0, 0, 0, 0, 0, 0, 
	 0, 0, 0, 0, 0, 0, 0, 0, 
	 0, 0, 0, 0, 0, 0, 0, 0, 
	 0, 0, 0,-1, 1, 0, 0, 0, 
	 0, 0, 0, 1,-1, 0, 0, 0, 
	 0, 0, 0, 0, 0, 0, 0, 0, 
	 0, 0, 0, 0, 0, 0, 0, 0, 
	 0, 0, 0, 0, 0, 0, 0, 0, 
};

// Flipable in 3 directions
#define FT3(a,b,c)	((1<<(a)) + (1<<(b)) + (1<<(c)))
#define FT3SE		FT3(DIR_E, DIR_SE, DIR_S)
#define FT3SW		FT3(DIR_W, DIR_SW, DIR_S)
#define FT3NE		FT3(DIR_E, DIR_NE, DIR_N)
#define FT3NW 		FT3(DIR_W, DIR_NW, DIR_N)

// Flipable in 5 directions
#define FT5(a,b,c,d,e)	((1<<(a)) + (1<<(b)) + (1<<(c)) + (1<<(d)) + (1<<(e)))
#define FT5S		FT5(DIR_E, DIR_SE, DIR_S, DIR_SW, DIR_W)
#define FT5E		FT5(DIR_N, DIR_NE, DIR_E, DIR_SE, DIR_S)
#define FT5W		FT5(DIR_N, DIR_NW, DIR_W, DIR_SW, DIR_S)
#define FT5N		FT5(DIR_E, DIR_NE, DIR_N, DIR_NW, DIR_W)

// Flipable in 8 directions
#define FT8		255

// Table of flipable directions in each position
int flip_table[64] = {
	FT3SE, FT3SE, FT5S,  FT5S,  FT5S,  FT5S,  FT3SW, FT3SW,
	FT3SE, FT3SE, FT5S,  FT5S,  FT5S,  FT5S,  FT3SW, FT3SW,
	FT5E,  FT5E,  FT8,   FT8,   FT8,   FT8,   FT5W,  FT5W,
	FT5E,  FT5E,  FT8,   FT8,   FT8,   FT8,   FT5W,  FT5W,
	FT5E,  FT5E,  FT8,   FT8,   FT8,   FT8,   FT5W,  FT5W,
	FT5E,  FT5E,  FT8,   FT8,   FT8,   FT8,   FT5W,  FT5W,
	FT3NE, FT3NE, FT5N,  FT5N,  FT5N,  FT5N,  FT3NW, FT3NW,
	FT3NE, FT3NE, FT5N,  FT5N,  FT5N,  FT5N,  FT3NW, FT3NW
};

		// No default initialization to speed up search
byte_board_info::byte_board_info()
{
}

byte_board_info::byte_board_info(const byte_board_type *board_init)
{
	*this = board_init;
}

byte_board_info& byte_board_info::operator=(const byte_board_type *board_init)
{
	memcpy(board, board_init, sizeof(byte_board_type));
	move = NUM_MOVE;
	board_iterator	iter;
	iter.init_pos();
	do {
		if (is_empty(iter.pos))
			move--;
	} while (iter.next());
	if (move < 0)
		throw std::runtime_error(_("invalid initial board"));
	hash = get_hash(this);
	return *this;
}

byte_board_info& byte_board_info::operator=(const byte_board_info& src)
{
	memcpy(this, &src, sizeof(byte_board_info));
	return *this;
}

void byte_board_info::set_pos(int color, int pos)
{
	board[pos] = color;
	move = NUM_MOVE;
	board_iterator	iter;
	iter.init_pos();
	do {
		if (is_empty(iter.pos))
			move--;
	} while (iter.next());
	if (move < 0)
		move = 0;	// Assume edit not finished
	hash = get_hash(this);
}

bool	byte_board_info::can_play_nocheck(int color, int pos) const
{
	board_dir_iterator	iter;
	for (int dir = DIR_START; dir <= DIR_END; ++dir) {

		if (flip_table[pos] & (1 << dir)) {
			iter.init_pos(pos);

			int	count = 0;
			while (iter.next_dir(dir)) {
				piece_type p = board[iter.pos];
				if (p == EMPTY) {
					break;
				}
				else if (p == color) {
					if (count) {
						return true;
					}
					else {
						break;
					}
				}
				else {
					count++;
				}
			}
		}
	}
	return false;
}

bool	byte_board_info::can_play_nocheck(int color, int pos, 
				     int dir_count[NUM_DIR]) const
{
	bool	ret = false;
	board_dir_iterator	iter;
	for (int dir = DIR_START; dir <= DIR_END; ++dir) {

		if (flip_table[pos] & (1 << dir)) {
			iter.init_pos(pos);

			int	count = 0;
			dir_count[dir] = 0;

			while (iter.next_dir(dir)) {
				piece_type p = board[iter.pos];
				if (p == EMPTY) {
					break;
				}
				else if (p == color) {
					if (count) {
						ret = true;
						dir_count[dir] = count;
					}
					break;
				}
				else {
					count++;
				}
			}
		}
		else
			dir_count[dir] = 0;
	}
	return ret;
}

bool	byte_board_info::can_play(int color) const
{
	board_iterator	iter;
	iter.init_pos();
	do {
		if (can_play(color, iter.pos))
			return true;
	} while (iter.next());
	return false;
}

// Assume can_play(...) is true for color and pos
void	byte_board_info::place_piece(int color, int pos)
{
	board[pos] = color;		// Place the new piece
	move++;
	hash ^= get_hash_piece(color, pos);

						// Flip pieces in all directions
	board_dir_iterator	iter;
	for (int dir = DIR_START; dir <= DIR_END; ++dir) {

		if (flip_table[pos] & (1 << dir)) {
			iter.init_pos(pos);

			int	count = 0;
			while (iter.next_dir(dir)) {
				piece_type p = board[iter.pos];
				if (p == EMPTY) {	// Not flanked
					break;
				}
				else if (p == color) {
					while (count) {	// Flanked
							// Flip
						iter.next_dir_nocheck(dir ^ 4);
						board[iter.pos] = color;
						hash ^= get_hash_flip(iter.pos);
						count--;
					}
					break;
				}
				else {
					count++;
				}
			}
		}
	}
}

// Assume can_play(...) is true for color and pos
void	byte_board_info::place_piece(int color, int pos, int dir_count[NUM_DIR])
{
	board[pos] = color;		// Place the new piece
	move++;
	hash ^= get_hash_piece(color, pos);

						// Flip pieces in all directions
	board_dir_iterator	iter;
	for (int dir = DIR_START; dir <= DIR_END; ++dir) {

		if (flip_table[pos] & (1 << dir)) {

			if (dir_count[dir]) {
				iter.init_pos(pos);

				int count = dir_count[dir];
				while (count) {
					iter.next_dir(dir);
					board[iter.pos] = color;
					hash ^= get_hash_flip(iter.pos);
					count--;
				}
			}
		}
	}
}

int	byte_board_info::board_diff_score() const
{
	int	score = 0;
	board_iterator	iter;
	iter.init_pos();
	do {
		score += board[iter.pos];
	} while (iter.next());
	return score;
}

int	byte_board_info::board_black_score() const
{
	int	score = 0;
	board_iterator	iter;
	iter.init_pos();
	do {
		if (board[iter.pos] == BLACK)
			score++;
	} while (iter.next());
	return score;
}

int	byte_board_info::board_white_score() const
{
	int	score = 0;
	board_iterator	iter;
	iter.init_pos();
	do {
		if (board[iter.pos] == WHITE)
			score++;
	} while (iter.next());
	return score;
}

bool operator==(const byte_board_info &board1, const byte_board_info &board2)
{
	if (memcmp(board1.board, board2.board, 64))
		return false;
	return true;
}
